基于SpringBoot的MyBatisPlus+Druid+AbstractRoutingDataSource的动态数据源实现方案

您所在的位置:网站首页 springboot 动态创建bean 基于SpringBoot的MyBatisPlus+Druid+AbstractRoutingDataSource的动态数据源实现方案

基于SpringBoot的MyBatisPlus+Druid+AbstractRoutingDataSource的动态数据源实现方案

2023-07-02 03:58| 来源: 网络整理| 查看: 265

一 概述

动态数据源是指在程序运行时可以动态切换数据源的技术。当我们具有相同的数据库结构、相同的Java实体映射,想要在上层进行数据源的切换(数据库层面)来查询不同的数据库时,便可采用动态数据源技术。 网络上也有很多的动态数据源的实现方案,大多是在项目配置文件中提前声明好主数据源、从数据源1、从数据源2(一主多从),但这种方式不是我想要的,我希望能够在代码端进行数据源的创建、初始化、查询、切换。 此文章将会利用SpringBoot的AbstractRoutingDataSource+DruidDataSource+MyBatisPlus进行动态数据源的实现与说明。

1 版本说明 Java - 8SpringBoot - 2.6.14MyBatsiPlus(mybatis-plus-boot-starter) - 3.5.3druid(alibaba) - 1.1.21数据库 - pg(不重要) 2 关键依赖 com.baomidou mybatis-plus-boot-starter 3.5.3 com.alibaba druid-spring-boot-starter 1.1.21 3 数据库与测试数据说明

有两部分数据库,一部分为系统数据源库,存储需要动态切换的数据源信息,例如数据库的host、port、db、user、password等等; image.png 另一部分为我们需要进行切换的库,他们具有相同的数据库表结构,存储我们的业务数据,示例中只列举了两个业务库,实际可自由扩展,由代码端进行数据库的创建、表的初始化、数据源记录的添加。 image.png

4 实体 @Data @TableName(value = "sys_datasource", autoResultMap = true) public class DataSourceEntity implements Serializable { private static final long serialVersionUID = 1L; public static final String DriverClassName = "org.postgresql.Driver"; @TableId(value = "id", type = IdType.AUTO) private Integer id; @TableField(value = "host") private String host; @TableField(value = "port") private Integer port; @TableField(value = "database") private String database; @TableField(value = "user") private String user; @TableField(value = "password") private String password; public String getUrl(){ return StrUtil.format("jdbc:postgresql://{}:{}/{}", host,port,database); } } @Data @TableName("data_people", autoResultMap = true) public class PeopleEntity implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; @TableField(value = "name") private String name; @TableField(value = "age") private Integer age; } 5 项目结构

image.png

二 实现效果

PeopleController中定义查询接口,同时接口需要传入数据源id参数,来查询指定数据源中的数据。

@RestController @RequestMapping("/people") @AllArgsConstructor @Slf4j public class PeopleController { private final DataSourceService dataSourceService; private final PeopleService peopleService; @GetMapping("/list/{dataSourceId}") public Object list(@PathVariable(value = "dataSourceId") Integer dataSourceId) { // 切换到指定数据源 if (!dataSourceService.changeDataSource(dataSourceId)) { return "指定数据源失败"; } List list = peopleService.list(); // 切换回原有数据源 dataSourceService.restoreDataSource(); return list; } }

image.png

三 具体实现 1 DataSourceIdHolder - 数据源id存放类

就像是之前做动态表名一样,因为接口进来后是在不同的线程中,需要有一个地方存放当前线程要查询的数据源id,所以使用ThreadLocal来做。ThreadLocal此处存储Integer整形数据,即数据源记录的id(整形自增id)。 此处也需要考虑避免内存泄漏的问题,需要确保set后remove即可,考虑到不希望在别的地方出现太多DataSourceIdHolder的引用,此步骤交给了DataSource的Service层来包装。

/** * 数据源id存放类 */ @Slf4j public class DataSourceIdHolder { /** * 线程级别的私有变量 */ private static final ThreadLocal DATASOURCEID_HOLDER = new ThreadLocal(); /** * 指定数据源 */ public static void setDataSource(Integer dataSourceId) { DATASOURCEID_HOLDER.set(dataSourceId); log.info("数据源Holder-设置数据源. threadId:[{}] dataSourceId:[{}]", Thread.currentThread().getId(), dataSourceId); } /** * 获取数据源 * * @return */ public static Integer getDataSource() { Integer dataSourceId = DATASOURCEID_HOLDER.get(); log.info("数据源Holder-获取数据源. threadId:[{}] dataSourceId:[{}]", Thread.currentThread().getId(), dataSourceId); return dataSourceId; } /** * 删除数据源 */ public static void removeDataSource() { DATASOURCEID_HOLDER.remove(); log.info("数据源Holder-删除数据源. threadId:[{}] dataSourceId:[{}]", Thread.currentThread().getId(), DATASOURCEID_HOLDER.get()); } } 2 DynamicRoutingDataSource - 动态路由数据源

AbstractRoutingDataSource(SpringBoot体系中的)是动态数据源实现的核心,它以Map的形式存储了我们定义的多个数据源,它通过determineCurrentLookupKey()方法返回一个key值,这个key值与上述Map中进行关联,来决定采用哪个数据源来返回数据库Connection对象进行后续的数据库操作,但本质上他也是一个数据源,实现了DataSource接口。 我们通过重写它的determineCurrentLookupKey()方法,从我们第一步“DataSourceIdHolder - 数据源id存放类”中来拿到要切换的数据源,来实现数据源的切换。

/** * 动态路由数据源 */ @Slf4j @Data public class DynamicRoutingDataSource extends AbstractRoutingDataSource { /** * 存储我们注册的数据源 */ private volatile Map customDataSources; @Override protected Object determineCurrentLookupKey() { // 指定本次查询要使用的数据源LookupKey // (1)LookupKey与dataSourceId为一对一的关系 // (2)dataSourceId来自DataSourceIdHolder(ThreadLocal) // (3)当此方法返回null时采用默认的数据源(即主数据源) // 判断DataSourceIdHolder中是否指定数据源 Integer dataSourceId = DataSourceIdHolder.getDataSource(); boolean isSetDataSourceInHolder = dataSourceId != null && dataSourceId > 0; if (!isSetDataSourceInHolder) { // 未指定返回null(后续查询使用的数据源采用默认数据源) log.info("LookupKey未指定,采用默认数据源. Holder中未指定数据源."); return null; } // 判断已经创建的数据源中是否包含指定的数据源id boolean isExistDataSourceInCustom = this.customDataSources.containsKey(dataSourceId); if (isExistDataSourceInCustom) { log.info("LookupKey已指定. dataSourceIdFromHolder:[{}]", dataSourceId); return dataSourceId; } else { log.info("LookupKey已指定,但数据源未注册,采用默认数据源. dataSourceId:[{}]", dataSourceId); return null; } } /** * 检查数据源是否已经创建,如果不存在则创建 */ public boolean checkOrCreateDataSource(DataSourceEntity dataSourceEntity) { if (this.customDataSources == null) this.customDataSources = new HashMap(); boolean result = false; Integer dataSourceId = dataSourceEntity.getId(); boolean isRegistered = this.customDataSources.containsKey(dataSourceId); if (isRegistered) { //检查之前创建的数据源现在是否连接正常 boolean isHealthy = checkDruidDataSource((DruidDataSource) this.customDataSources.get(dataSourceId)); if (!isHealthy) { log.warn("数据源非健康状态,删除后重新创建数据源..."); result = delete(dataSourceId) && create(dataSourceEntity); } } else { log.info("数据源未创建,创建数据源..."); result = create(dataSourceEntity); } return result; } /** * 创建数据源 * * @param dataSourceEntity 数据源实体 */ private boolean create(DataSourceEntity dataSourceEntity) { Integer dataSourceId = dataSourceEntity.getId(); String dataSourceName = getDataSourceName(dataSourceId); // 检查数据源链接配置 if (!this.checkConnectionParameters(dataSourceEntity.getUrl(), dataSourceEntity.getUser(), dataSourceEntity.getPassword())) return false; // 构建Druid数据源 DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setName(dataSourceName); druidDataSource.setDriverClassName(DataSourceEntity.DriverClassName); druidDataSource.setUrl(dataSourceEntity.getUrl()); druidDataSource.setUsername(dataSourceEntity.getUser()); druidDataSource.setPassword(dataSourceEntity.getPassword()); druidDataSource.setMaxActive(20); druidDataSource.setMinIdle(5); druidDataSource.setMaxWait(6000); //获取连接最大等待时间,单位毫秒 druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,防止取到的连接不可用 druidDataSource.setValidationQuery("select 1"); // Druid数据源初始化 try { druidDataSource.init(); } catch (SQLException e) { log.error("创建数据源-数据源初始化失败. id:[{}] name:[{}} 异常信息:[{}]", dataSourceId, dataSourceName, e.getMessage()); return false; } // 记录到自定义数据源Map中 this.customDataSources.put(dataSourceId, druidDataSource); // 将map赋值给父类的TargetDataSources super.setTargetDataSources(this.customDataSources); // 将TargetDataSources中的连接信息放入resolvedDataSources管理 super.afterPropertiesSet(); log.info("创建数据源-成功. 数据源id:[{}] name:[{}} ", dataSourceId, dataSourceName); return true; } /** * 删除数据源 * * @param dataSourceId 数据源id */ private boolean delete(Integer dataSourceId) { Set druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances(); for (DruidDataSource dataSource : druidDataSourceInstances) { String dataSourceName = this.getDataSourceName(dataSourceId); if (!dataSource.getName().equals(dataSourceName)) continue; //从实例中移除当前dataSource DruidDataSourceStatManager.removeDataSource(dataSource); // 自定义的数据源记录中删除 this.customDataSources.remove(dataSourceId); // 将map赋值给父类的TargetDataSources super.setTargetDataSources(this.customDataSources); // 将TargetDataSources中的连接信息放入resolvedDataSources管理 super.afterPropertiesSet(); log.info("删除数据源-成功. dataSourceId:[{}] dataSourceName:[{}]", dataSourceId, dataSourceName); return true; } log.warn("删除数据源-失败,未找到指定数据源. dataSourceId:[{}]", dataSourceId); return false; } /** * 根据数据源id(Integer)获取数据源name *

* 格式采用:ds_datasourceId * * @param dataSourceId * @return */ private String getDataSourceName(Integer dataSourceId) { if (dataSourceId == null || dataSourceId connection = DriverManager.getConnection(url, user, password); isHealthy = connection != null; } catch (SQLException exception) { isHealthy = false; log.error("数据源检查-失败,配置有错误,无法正确获取链接. url:[{}] user:[{}] password:[{}]", url, user, password); } finally { if (isHealthy) try { connection.close(); } catch (SQLException exception) { log.warn("数据源检查-警告,检查后无法关闭链接. url:[{}] user:[{}] password:[{}] exception:[{}]", url, user, password, exception.getMessage()); } } return isHealthy; } /** * 检查Druid数据源 *

* 逻辑:通过数据源获取内部连接对象,看能够正确获取连接对象 * * @param druidDataSource * @return */ private boolean checkDruidDataSource(DruidDataSource druidDataSource) { boolean isHealthy = true; DruidPooledConnection connection = null; try { connection = druidDataSource.getConnection(); } catch (SQLException exception) { //抛异常了说明连接失效,则删除现有连接 log.error("数据源健康检查,获取连接对象失败. 异常信息:[{}]", exception.getMessage()); isHealthy = false; } finally { //如果连接正常关闭连接 if (connection != null) { try { connection.close(); } catch (SQLException exception) { log.warn("数据源健康检查,关闭连接对象失败. 异常信息:[{}]", exception.getMessage()); } } } return isHealthy; } 3 DruidDBConfig - DruidDB配置类

此配置类中我们定义了四个bean:

mainDataSource(DataSource) 此bean作为主数据源,通过我们在配置文件中的配置进行构建;返回DruidDataSource的实例(数据库连接池的管理); dynamicDataSource(DynamicRoutingDataSource) 此bean作为动态数据源;将上一步定义的mainDataSource设置为默认数据源与目标数据源;默认数据源是我们未指定数据源时采用的数据源;目标数据源是 三-2 中determineCurrentLookupKey()中决定要采用哪个的数据源集合,本实例的另外两个数据库(数据源)就存放在此; sqlSessionFactory(SqlSessionFactory) SqlSessionFactory负责将 MyBatis的Configuration对象的信息与连接池和数据源相关联,从而创建 SqlSession 的单个实例;此处有坑,放在后面讲 transactionManager(DataSourceTransactionManager) 事务管理 /** * DruidDB配置类 */ @Configuration @Slf4j public class DruidDBConfig { @Value("${spring.datasource.url}") private String dbUrl; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; @Value("${spring.datasource.driver-class-name}") private String driverClassName; // 连接池连接信息 @Value("${spring.datasource.druid.initial-size}") private int initialSize; @Value("${spring.datasource.druid.min-idle}") private int minIdle; @Value("${spring.datasource.druid.max-active}") private int maxActive; @Value("${spring.datasource.druid.max-wait}") private int maxWait; /** * 默认数据源bean * * @return * @throws SQLException */ @Bean @Primary @Qualifier("mainDataSource") public DataSource dataSource() throws SQLException { DruidDataSource datasource = new DruidDataSource(); // 基础连接信息 datasource.setUrl(this.dbUrl); datasource.setUsername(username); datasource.setPassword(password); datasource.setDriverClassName(driverClassName); // 连接池连接信息 datasource.setInitialSize(initialSize); datasource.setMinIdle(minIdle); datasource.setMaxActive(maxActive); datasource.setMaxWait(maxWait); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 datasource.setPoolPreparedStatements(false); datasource.setMaxPoolPreparedStatementPerConnectionSize(20); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用 datasource.setTestOnBorrow(true); //建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 datasource.setTestWhileIdle(true); //用来检测连接是否有效的sql // datasource.setValidationQuery("select 1 from dual"); datasource.setValidationQuery("select 1"); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 datasource.setTimeBetweenEvictionRunsMillis(60000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000 datasource.setMinEvictableIdleTimeMillis(180000); datasource.setKeepAlive(true); return datasource; } /** * 自定义的动态数据源bean * * @return * @throws SQLException */ @Bean(name = "dynamicDataSource") @Qualifier("dynamicDataSource") public DynamicRoutingDataSource dynamicDataSource() throws SQLException { DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource(); //配置缺省的数据源 dynamicDataSource.setDefaultTargetDataSource(dataSource()); Map targetDataSources = new HashMap(); //额外数据源配置 TargetDataSources targetDataSources.put("mainDataSource", dataSource()); dynamicDataSource.setTargetDataSources(targetDataSources); return dynamicDataSource; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { //用mybatis的这里会有点区别,mybatis用的是SqlSessionFactoryBean MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); // 此处有坑,放在后面讲 return sqlSessionFactoryBean.getObject(); } /** * 将动态数据加载类添加到事务管理器 */ @Bean public DataSourceTransactionManager transactionManager(DynamicRoutingDataSource dataSource) { return new DataSourceTransactionManager(dataSource); } } 4 DataSource部分的Mapper与Service层

DataSourceService层包含两个接口定义,用于在其它Controller层中进行数据源的指定,一个用于指定数据源,另外一个用于恢复默认数据源。第二个接口void restoreDataSource()便是对DataSourceIdHolder在当前线程中存储的数据源id值的清除。

@Mapper public interface DataSourceMapper extends BaseMapper { } public interface DataSourceService { /** * 设置为指定数据源 * * @param dataSourceId * @return */ boolean changeDataSource(Integer dataSourceId); /** * 恢复默认数据源 */ void restoreDataSource(); } @Service @Slf4j @AllArgsConstructor public class DataSourceServiceImpl implements DataSourceService { private final DataSourceMapper dataSourceMapper; private final DynamicRoutingDataSource dynamicRoutingDataSource; @Override public boolean changeDataSource(Integer dataSourceId) { //切到默认数据源,查询所有的数据源配置 DataSourceIdHolder.removeDataSource(); // TODO:高频查询,按需引入Redis DataSourceEntity dataSourceEntity = dataSourceMapper.selectById(dataSourceId); if (dataSourceEntity == null) { log.warn("未找到指定数据源. dataSourceId:[{}]", dataSourceId); return false; } log.info("已找到数据源,dataSourceId:[{}]", dataSourceId); //判断连接是否存在,不存在就创建 boolean checkOrCreateResult = dynamicRoutingDataSource.checkOrCreateDataSource(dataSourceEntity); if (checkOrCreateResult) { // DataSourceIdHolder中记录使用的数据源Id,供下次查询时使用指定数据源 DataSourceIdHolder.setDataSource(dataSourceId); } return checkOrCreateResult; } @Override public void restoreDataSource() { //切回主数据源 DataSourceIdHolder.removeDataSource(); } }

People部分的Mapper与Service层没有什么内容,是很薄的一层,采用MyBatisPlus的BaseMapper、IService、ServiceImpl进行扩展即可。

5 Controller - 在本层进行数据源的切换 @RestController @RequestMapping("/people") @AllArgsConstructor @Slf4j public class PeopleController { private final DataSourceService dataSourceService; private final PeopleService peopleService; @GetMapping("/list/{dataSourceId}") public Object list(@PathVariable(value = "dataSourceId") Integer dataSourceId) { // 切换指定数据源 if (!dataSourceService.changeDataSource(dataSourceId)) { return "指定数据源失败"; } // 优雅一些的写法,采用Assert断言工具类确保切换数据源成功,在外部再增加全局异常捕获,对此类异常统一处理 // Assert.isTrue(dataSourceService.changeDataSource(dataSourceId),"指定数据源失败"); List list = peopleService.list(); // 恢复默认数据源(当前线程结束前清空ThreadLocal中存放的值) dataSourceService.restoreDataSource(); return list; } } 四 AbstractRoutingDataSource源码关键部分解读

AbstractRoutingDataSource是Spring框架中提供的一个抽象类,用于实现动态数据源。它继承自javax.sql.DataSource接口,并重写了getConnection()方法,该方法会根据当前线程绑定的数据源key来获取相应的数据源,并返回一个连接。 AbstractRoutingDataSource中有Map targetDataSources来存放我们需要切换的数据源; image.png AbstractRoutingDataSource中getConnection()方法进行了特殊处理,通过方法determineCurrentLookupKey()返回的lookupKey来决定使用哪个数据源来返回数据库连接对象。因此示例中我们对determineCurrentLookupKey()方法进行了重写,来实现我们想要的效果(从Holder中拿到数据源key进行切换)。 image.png image.png

五 一些“坑” 1 为什么添加动态数据源后Mapper中定义的自定义查询方法调用提示无效绑定

上述示例是用作预研,都是最简单的CRUD测试,但实际项目中会在Mapper中添加我们自定义的查询方法与结果映射,添加动态数据源后调用Mapper中自定义的方法便报错提示Invalid bound statement (not found)的错误。这类问题出现时,往往我们会检查下面内容:

mapper.xml中的方法标签id与mapper接口中的方法名是否一致;mapper.xml中头部的namespace是否与mapper接口所在的路径一致;spring配置文件中mybatis(mybatis-plus)配置中对mapper.xml文件路径配置是否正确;Mapper是否使用@Mapper注解声明,或启动类是否添加Mapper的扫描路径@MapperScan…

当这些都检查完了发现依然不行,就需要去深入检查一下是否是动态数据源的引入出现的问题。排查过程不再赘述了,直接来看问题点。 我们知道,SqlSessionFactory负责将 MyBatis的Configuration对象的信息与连接池和数据源相关联,从而创建 SqlSession 的单个实例进而执行数据库操作。换句话说,也就是需要在SqlSessionFactory中指定数据源与MyBatis的配置,比如Mapper路径、MetaObjectHandler(字段填充)!回过头来看一下三-3中DruidDBConfig(DruidDB配置类)中的SqlSessionFactorybean要如何修改:

@Bean public SqlSessionFactory sqlSessionFactory() throws Exception { //用mybatis的这里会有点区别,mybatis用的是SqlSessionFactoryBean MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); return sqlSessionFactoryBean.getObject(); } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { //用mybatis的这里会有点区别,mybatis用的是SqlSessionFactoryBean MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); // 指定Mapper文件的位置 sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml")); return sqlSessionFactoryBean.getObject(); }

通过更改可以看到,我们给sqlSessionFactoryBean指定了Mapper文件的路径。 在这里,我们使用了Spring提供的PathMatchingResourcePatternResolver类来获取指定路径下的所有Mapper文件,并将其设置到MybatisSqlSessionFactoryBean对象中。 具体来说,代码中的"classpath*:/mapper/.xml"表示从classpath中查找所有以.xml结尾的文件,并且文件路径中包含/mapper/的文件。其中,classpath:表示在所有的classpath中查找,包括jar包中的classpath;/mapper/表示在mapper目录下查找;*.xml表示查找以.xml结尾的文件。例如,如果MyBatis的Mapper文件位于/src/main/resources/mapper/目录下,那么这行代码就会查找该目录下所有以.xml结尾的文件。

2 为什么添加动态数据源后MyBatisPlus的MetaObjectHandler进行字段填充失效

通常我们会给Entity(数据库表)添加创建时间、更新时间等字段,我们希望在创建一条记录时,指定当前时间为创建时间,在更新这条记录时,指定更新时间为当前时间。在MyBatisPlus项目中,我们可以自定义元对象填充处理类实现MyBatisPlus的MetaObjectHandler接口,对我们需要处理的字段在插入或者更新时进行处理。 基于MyBatisPlus实现针对于创建时间、更新时间字段进行填充,具体示例实现如下: (1)第一步,编写自定义字段填充处理器,实现MyBatisPlus的MetaObjectHandler接口,添加具体操作

@Slf4j @Component public class TimeFieldFillHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐) } @Override public void updateFill(MetaObject metaObject) { log.info("start update fill ...."); // MetaObjectHandler提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null则不填充 // this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐) // 更新时间字段采用此方式赋值,不管是否已经有值 this.setFieldValByName("updateTime",LocalDateTime.now(),metaObject); } }

(2)第二步,在要进行处理的Entity中的属性上添加注解

/** * 创建时间 */ @TableField(value = "create_time", fill = FieldFill.INSERT) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime createTime; /** * 更新时间 */ @TableField(value = "update_time", fill = FieldFill.UPDATE) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime updateTime;

这个示例在原本非动态数据源的项目中可以正常生效,但添加动态数据源支持后,此字段填充配置无法生效。首先来看一下官方贴出的注意事项: image.png

我们知道,SqlSessionFactory负责将 MyBatis的Configuration对象的信息与连接池和数据源相关联,从而创建 SqlSession 的单个实例进而执行数据库操作。换句话说,也就是需要在SqlSessionFactory中指定数据源与MyBatis的配置,比如Mapper路径、MetaObjectHandler(字段填充)!回过头来看一下 三-3 DruidDBConfig(DruidDB配置类)中的SqlSessionFactorybean要如何修改:…

回过头再看我在五-1问题排查中说的这句话,结合字段填充器的本质是entity的属性设置值,我们可以推断出来实体行为受到了影响,也就是MyBatis配置产生的影响。回过头来看一下 三-3 DruidDBConfig(DruidDB配置类)中的SqlSessionFactorybean针对于字段填充失效要如何修改:

@Bean public SqlSessionFactory sqlSessionFactory() throws Exception { //用mybatis的这里会有点区别,mybatis用的是SqlSessionFactoryBean MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); return sqlSessionFactoryBean.getObject(); } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { //用mybatis的这里会有点区别,mybatis用的是SqlSessionFactoryBean MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); // 指定Mapper文件的位置 sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml")); // 构建MyBatisPlus全局配置对象,用于指定元对象字段填充 GlobalConfig globalConfig = new GlobalConfig(); globalConfig.setMetaObjectHandler(new TimeFieldFillHandler()); sqlSessionFactoryBean.setGlobalConfig(globalConfig); return sqlSessionFactoryBean.getObject(); }

这里我们构建了一个MyBatisPlus的全局配置对象,配置指定了MetaObjectHandler。到此位置,问题就得到解决了。

六 参考与引用

MyBatisPlus - 自动填充功能 知乎 - 但偏偏雨渐渐 - 《SpringBoot+Mybatis-Plus实现动态数据源切换》



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3